package com.wcs.exam.controller.admin.biz;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.wcs.exam.common.base.BaseBiz;
import com.wcs.exam.common.base.Result;
import com.wcs.exam.common.base.exception.BaseException;
import com.wcs.exam.common.base.page.Page;
import com.wcs.exam.common.base.page.PageUtil;
import com.wcs.exam.common.constant.RedisConstant;
import com.wcs.exam.common.enums.ExamTypeEnum;
import com.wcs.exam.common.enums.ResultShowRuleEnum;
import com.wcs.exam.common.enums.UserExamStatusEnum;
import com.wcs.exam.common.enums.ValidityDateTypeEnum;
import com.wcs.exam.common.util.BeanUtil;
import com.wcs.exam.controller.admin.req.*;
import com.wcs.exam.controller.admin.resp.AdminExamPageResp;
import com.wcs.exam.controller.admin.resp.AdminExamViewResp;
import com.wcs.exam.controller.biz.CategoryCommonBiz;
import com.wcs.exam.dao.*;
import com.wcs.exam.dao.impl.mapper.entity.*;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Admin-考试
 *
 * @author wcs
 * @since 2025-03-13
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AdminExamBiz extends BaseBiz {

    @NotNull
    private final RedissonClient redissonClient;

    @NotNull
    private final CategoryCommonBiz categoryCommonBiz;


    @NotNull
    private final ExamUserDao examUserDao;
    @NotNull
    private final ExamUserAnswerDao examUserAnswerDao;
    @NotNull
    private final ExamUserAppealDao examUserAppealDao;
    @NotNull
    private final ExamUserRecordDao examUserRecordDao;
    @NotNull
    private final PaperDao paperDao;
    @NotNull
    private final CategoryDao categoryDao;
    @NotNull
    private final ExamDao dao;

    /**
     * 考试-分页
     *
     * @param req 考试-分页查询参数
     * @return 考试
     */
    public Result<Page<AdminExamPageResp>> page(AdminExamPageReq req) {
        // 获取查询分类
        Set<Long> categoryIds = new HashSet<>();
        if (ObjectUtil.isNotNull(req.getCategoryId())) {
            categoryIds.add(req.getCategoryId());

            if (ObjectUtil.isNull(req.getOnlyCurrentCategory()) || Boolean.FALSE.equals(req.getOnlyCurrentCategory())) {
                List<Long> categoryChildrenIds = categoryCommonBiz.listChildrenIdById(req.getCategoryId());
                if (CollectionUtil.isNotEmpty(categoryChildrenIds)) {
                    categoryIds.addAll(categoryChildrenIds);
                }
            }
        }

        Map<Long, Paper> paperMap = new HashMap<>();
        Set<Long> paperIds = new HashSet<>();
        if (ObjectUtil.isNotNull(req.getPaperId())) {
            paperIds.add(req.getPaperId());
        }
        if (ObjectUtil.isNotNull(req.getRandomCombine()) || ObjectUtil.isNotNull(req.getDerangement()) || ObjectUtil.isNotNull(req.getQuestionDerangement()) || ObjectUtil.isNotNull(req.getOptionDerangement())) {
            List<Paper> paperList = paperDao.lambdaQuery()
                    .in(CollectionUtil.isNotEmpty(paperIds), Paper::getId, paperIds)
                    .eq(ObjectUtil.isNotNull(req.getRandomCombine()), Paper::getRandomCombine, req.getRandomCombine())
                    .eq(ObjectUtil.isNotNull(req.getDerangement()), Paper::getDerangement, req.getDerangement())
                    .eq(ObjectUtil.isNotNull(req.getQuestionDerangement()), Paper::getQuestionDerangement, req.getQuestionDerangement())
                    .eq(ObjectUtil.isNotNull(req.getOptionDerangement()), Paper::getOptionDerangement, req.getOptionDerangement())
                    .list();
            if (CollectionUtil.isEmpty(paperList)) {
                return Result.success(new Page<>());
            }
            paperList.forEach(paper -> paperMap.put(paper.getId(), paper));

            paperIds.clear();
            paperIds.addAll(paperMap.keySet());
        }

        LocalDateTime endGmtCreate = ObjectUtil.isNotNull(req.getEndGmtCreate()) ? req.getEndGmtCreate().plusMinutes(1) : null;
        LocalDateTime endRegistrationStartTime = ObjectUtil.isNotNull(req.getEndRegistrationStartTime()) ? req.getEndRegistrationStartTime().plusMinutes(1) : null;
        LocalDateTime endRegistrationEndTime = ObjectUtil.isNotNull(req.getEndRegistrationEndTime()) ? req.getEndRegistrationEndTime().plusMinutes(1) : null;
        LocalDateTime endExamStartTime = ObjectUtil.isNotNull(req.getEndExamStartTime()) ? req.getEndExamStartTime().plusMinutes(1) : null;

        // 处理查询条件
        LambdaQueryWrapper<Exam> wrapper = new LambdaQueryWrapper<Exam>()
                .in(Exam::getExamType, ExamTypeEnum.MOCK.getCode(), ExamTypeEnum.EXAMINATION.getCode())
                .eq(ObjectUtil.isNotNull(req.getExamType()), Exam::getExamType, req.getExamType())
                .like(StrUtil.isNotBlank(req.getExamName()), Exam::getExamName, req.getExamName())
                .in(CollectionUtil.isNotEmpty(categoryIds), Exam::getCategoryId, categoryIds)
                .in(CollectionUtil.isNotEmpty(paperIds), Exam::getPaperId, paperIds)
                .like(StrUtil.isNotBlank(req.getDescription()), Exam::getDescription, req.getDescription())
                .eq(ObjectUtil.isNotNull(req.getPublishStatus()), Exam::getPublishStatus, req.getPublishStatus())
                .eq(ObjectUtil.isNotNull(req.getStatus()), Exam::getStatus, req.getStatus())
                .ge(ObjectUtil.isNotNull(req.getBeginGmtCreate()), Exam::getGmtCreate, req.getBeginGmtCreate())
                .lt(ObjectUtil.isNotNull(endGmtCreate), Exam::getGmtCreate, endGmtCreate)
                .ge(ObjectUtil.isNotNull(req.getBeginRegistrationStartTime()), Exam::getRegistrationStartTime, req.getBeginRegistrationStartTime())
                .lt(ObjectUtil.isNotNull(endRegistrationStartTime), Exam::getRegistrationStartTime, endRegistrationStartTime)
                .ge(ObjectUtil.isNotNull(req.getEndRegistrationEndTime()), Exam::getRegistrationEndTime, req.getEndRegistrationEndTime())
                .lt(ObjectUtil.isNotNull(endRegistrationEndTime), Exam::getRegistrationEndTime, endRegistrationEndTime)
                .ge(ObjectUtil.isNotNull(req.getBeginExamStartTime()), Exam::getExamStartTime, req.getEndRegistrationEndTime())
                .lt(ObjectUtil.isNotNull(endExamStartTime), Exam::getExamStartTime, endExamStartTime)
                .eq(ObjectUtil.isNotNull(req.getAllowableLate()), Exam::getAllowableLate, req.getAllowableLate())
                // 排序
                .orderByDesc(Exam::getSort)
                .orderByDesc(Exam::getId);

        // 分页查询
        IPage<Exam> page = dao.page(PageDTO.of(req.getPageCurrent(), req.getPageSize()), wrapper);
        if (ObjectUtil.isNull(page) || CollectionUtil.isEmpty(page.getRecords())) {
            return Result.success(new Page<>());
        }
        Page<AdminExamPageResp> respPage = PageUtil.transform(page, AdminExamPageResp.class);

        if (CollectionUtil.isEmpty(paperMap)) {
            Set<Long> respPaperIds = respPage.getList().stream().map(AdminExamPageResp::getPaperId).collect(Collectors.toSet());
            List<Paper> paperList = paperDao.listByIds(respPaperIds);
            if (CollectionUtil.isNotEmpty(paperList)) {
                paperList.forEach(item -> paperMap.put(item.getId(), item));
            }
        }

        Set<Long> respCategoryIds = respPage.getList().stream().map(AdminExamPageResp::getCategoryId).collect(Collectors.toSet());
        Map<Long, String> categoryNameMap = new HashMap<>();
        if (CollectionUtil.isNotEmpty(respCategoryIds)) {
            List<Category> categoryList = categoryDao.listByIds(respCategoryIds);
            if (CollectionUtil.isNotEmpty(categoryList)) {
                categoryList.forEach(item -> categoryNameMap.put(item.getId(), item.getCategoryName()));
            }
        }

        // 处理分页响应信息
        for (AdminExamPageResp resp : respPage.getList()) {
            resp.setCategoryName(categoryNameMap.getOrDefault(resp.getCategoryId(), ""));

            Paper paper = paperMap.get(resp.getPaperId());
            if (ObjectUtil.isNotNull(paper)) {
                resp.setPaperName(paper.getPaperName());
                resp.setTotalScore(paper.getTotalScore());
                resp.setRandomCombine(paper.getRandomCombine());
                resp.setDerangement(paper.getDerangement());
                resp.setQuestionDerangement(paper.getQuestionDerangement());
                resp.setOptionDerangement(paper.getOptionDerangement());
            }
        }
        return Result.success(respPage);
    }

    /**
     * 考试-查看
     *
     * @param id 主键ID
     * @return 考试
     */
    public Result<AdminExamViewResp> view(Long id) {
        Exam exam = dao.getById(id);
        if (ObjectUtil.isNull(exam)) {
            return Result.error("考试不存在");
        }
        AdminExamViewResp resp = BeanUtil.copyProperties(exam, AdminExamViewResp.class);

        Paper paper = paperDao.getById(resp.getPaperId());
        resp.setPaperName(ObjectUtil.isNotNull(paper) ? paper.getPaperName() : "");

        Category category = categoryDao.getById(resp.getCategoryId());
        resp.setCategoryName(ObjectUtil.isNotNull(category) ? category.getCategoryName() : "");

        // 兼容前端编辑逻辑
        if (!ValidityDateTypeEnum.FIXED_DAY.getCode().equals(exam.getValidityDateType())) {
            resp.setDays(null);
        }
        return Result.success(resp);
    }

    /**
     * 考试-编辑发布状态
     *
     * @param req 考试-编辑发布状态对象
     * @return 编辑结果
     */
    public Result<String> editPublishStatus(AdminExamEditPublishStatusReq req) {
        // 获取校验考试
        Exam exam = dao.lambdaQuery()
                .eq(Exam::getId, req.getId())
                .one();
        if (ObjectUtil.isNull(exam)) {
            return Result.error("考试不存在");
        }

        // 更新考试
        Exam updateExam = BeanUtil.copyProperties(req, Exam.class);
        updateExam.setGmtCreate(null);
        updateExam.setGmtModified(null);
        if (dao.updateById(updateExam)) {
            return Result.success(toI18nSuccess());
        }
        return Result.error(toI18nFail());
    }

    /**
     * 考试-排序
     *
     * @param reqs 考试-排序对象
     * @return 排序结果
     */
    public Result<String> sort(List<AdminExamSortReq> reqs) {
        // 查询获取考试
        List<Exam> examList = dao
                .lambdaQuery()
                .in(Exam::getExamType, ExamTypeEnum.MOCK.getCode(), ExamTypeEnum.EXAMINATION.getCode())
                .in(Exam::getId, reqs.stream().map(AdminExamSortReq::getId).collect(Collectors.toSet()))
                .list();
        if (CollectionUtil.isEmpty(examList)) {
            return Result.success(toI18nSuccess());
        }

        RLock lock = redissonClient.getLock(RedisConstant.Exam.EXAM);
        try {
            boolean lockRes = lock.tryLock(RedisConstant.DEFAULT_WAIT_TIME, RedisConstant.DEFAULT_LEASE_TIME, TimeUnit.SECONDS);
            if (!lockRes) {
                log.warn("考试排序获取锁超时");
                return Result.error(toI18nFail());
            }

            dao.lambdaUpdate()
                    .in(Exam::getId, examList.stream().map(Exam::getId).collect(Collectors.toSet()))
                    .setDecrBy(Exam::getSort, Integer.MAX_VALUE)
                    .update();

            // 处理待更新排序的考试
            Map<Long, Integer> sortMap = new HashMap<>();
            reqs.forEach(item -> sortMap.put(item.getId(), item.getSort()));
            List<Exam> updateList = new ArrayList<>();
            for (Exam exam : examList) {
                Integer sort = sortMap.get(exam.getId());
                if (ObjectUtil.isNull(sort)) {
                    continue;
                }

                Exam updateExam = new Exam();
                updateExam.setId(exam.getId());
                updateExam.setSort(sort);
                updateList.add(updateExam);
            }

            // 批量更新考试排序
            if (CollectionUtil.isNotEmpty(updateList)) {
                dao.updateBatchById(updateList);
            }
            return Result.success(toI18nSuccess());
        } catch (InterruptedException e) {
            log.error("考试排序信息失败", e);
            Thread.currentThread().interrupt();
            throw new BaseException(toI18nFail());
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 考试-删除
     *
     * @param id ID主键
     * @return 删除结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<String> delete(Long id) {
        // 获取校验考试
        Exam exam = dao.getById(id);
        if (ObjectUtil.isNull(exam)) {
            return Result.error("考试不存在");
        }
        if (!(ExamTypeEnum.MOCK.getCode().equals(exam.getExamType()) || ExamTypeEnum.EXAMINATION.getCode().equals(exam.getExamType()))) {
            return Result.error("考试不存在");
        }

        // 删除考试
        boolean deleteRes = dao.removeById(id);

        // 删除考试用户
        examUserDao.lambdaUpdate().eq(ExamUser::getExamId, id).remove();

        // 删除考试用户答题
        examUserAnswerDao.lambdaUpdate()
                .eq(ExamUserAnswer::getExamType, exam.getExamType())
                .eq(ExamUserAnswer::getRelateId, id)
                .remove();

        // 删除考试人脸异常申请
        examUserAppealDao.lambdaUpdate().eq(ExamUserAppeal::getExamId, id).remove();

        // 删除考试用户答题记录
        examUserRecordDao.lambdaUpdate()
                .eq(ExamUserRecord::getExamType, exam.getExamType())
                .eq(ExamUserRecord::getRelateId, id)
                .remove();
        return deleteRes ? Result.success(toI18nSuccess()) : Result.error(toI18nFail());
    }

    /**
     * 考试-添加
     *
     * @param req 考试-添加对象
     * @return 添加结果
     */
    public Result<String> saveMock(AdminExamMockSaveReq req) {
        Exam exam = BeanUtil.copyProperties(req, Exam.class);
        // 参数校验
        String message = mockSaveUpdateParamValid(exam);
        if (StrUtil.isNotBlank(message)) {
            return Result.error(message);
        }

        RLock lock = redissonClient.getLock(RedisConstant.Exam.EXAM);
        try {
            boolean lockRes = lock.tryLock(RedisConstant.DEFAULT_WAIT_TIME, RedisConstant.DEFAULT_LEASE_TIME, TimeUnit.SECONDS);
            if (!lockRes) {
                log.warn("添加考试获取锁超时");
                return Result.error(toI18nFail());
            }
            Integer maxSort = dao.getMaxSort();
            exam.setExamType(ExamTypeEnum.MOCK.getCode());
            exam.setSort(maxSort + 1);

            boolean saveRes = dao.save(exam);
            return saveRes ? Result.success(toI18nSuccess()) : Result.error(toI18nFail());
        } catch (InterruptedException e) {
            log.error("添加考试信息失败", e);
            Thread.currentThread().interrupt();
            throw new BaseException(toI18nFail());
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 考试-编辑
     *
     * @param req 考试-编辑对象
     * @return 编辑结果
     */
    public Result<String> editMock(AdminExamMockEditReq req) {
        // 获取校验考试
        Exam exam = dao.getById(req.getId());
        if (ObjectUtil.isNull(exam) || !ExamTypeEnum.MOCK.getCode().equals(exam.getExamType())) {
            return Result.error("考试不存在");
        }

        // 更新考试
        Exam updateExam = BeanUtil.copyProperties(req, Exam.class);

        // 参数校验
        String message = mockSaveUpdateParamValid(updateExam);
        if (StrUtil.isNotBlank(message)) {
            return Result.error(message);
        }

        updateExam.setGmtCreate(null);
        updateExam.setGmtModified(null);
        boolean updateRes = dao.updateById(updateExam);
        return updateRes ? Result.success(toI18nSuccess()) : Result.error(toI18nFail());
    }

    /**
     * 考试-添加或更新参数校验
     *
     * @param exam 添加对象
     * @return 错误信息
     */
    private String mockSaveUpdateParamValid(Exam exam) {
        Paper paper = paperDao.getById(exam.getPaperId());
        if (ObjectUtil.isNull(paper)) {
            return "试卷不存在";
        }

        // 有效期
        if (ValidityDateTypeEnum.FIXED_DAY.getCode().equals(exam.getValidityDateType())) {
            if (ObjectUtil.isNull(exam.getDays())) {
                return "有效天数不能为空";
            }
        } else if (ValidityDateTypeEnum.FIXED_PERIOD.getCode().equals(exam.getValidityDateType())) {
            if (ObjectUtil.isNull(exam.getBeginTime()) || ObjectUtil.isNull(exam.getEndTime())) {
                return "考试（开始时间/结束时间）不能为空";
            }
            if (exam.getBeginTime().isAfter(exam.getEndTime())) {
                return "考试结束时间不能小于开始时间";
            }
            exam.setEndTime(exam.getEndTime().plusSeconds(59));
        }

        // 切屏限制
        if (Boolean.TRUE.equals(exam.getSwitchScreenLimit())) {
            if (ObjectUtil.isNull(exam.getSwitchScreenCount())) {
                return "考试切换屏数不能为空";
            }
            if (ObjectUtil.isNull(exam.getSwitchScreenTime())) {
                return "考试切换屏判断时间不能为空";
            }
        }

        // 成绩展示
        if (Boolean.TRUE.equals(exam.getResultShow())) {
            if (ObjectUtil.isNull(exam.getResultShowRule())) {
                return "考试成绩展示规则不能为空";
            }
            if (ResultShowRuleEnum.CUSTOM.getCode().equals(exam.getResultShowRule()) && ObjectUtil.isNull(exam.getResultShowTime())) {
                return "考试成绩展示时间不能为空";
            }

            if (Boolean.TRUE.equals(exam.getAnswerShow())) {
                if (ObjectUtil.isNull(exam.getAnswerShowRule())) {
                    return "作答内容展示规则不能为空";
                }
            }
        } else {
            exam.setAnswerShow(Boolean.FALSE);
            exam.setResultShowRank(Boolean.FALSE);
        }

        // 人脸抓拍
        if (Boolean.TRUE.equals(exam.getFaceCapture())) {
            if (ObjectUtil.isNull(exam.getFaceCaptureTime())) {
                return "考试人脸抓拍时间不能为空";
            }

            if (Boolean.FALSE.equals(exam.getFaceId())) {
                exam.setFaceCaptureDetection(Boolean.FALSE);
                exam.setFaceCaptureHighDetection(Boolean.FALSE);
            } else {
                if (Boolean.TRUE.equals(exam.getFaceCaptureDetection())) {
                    if (ObjectUtil.isNull(exam.getFaceCaptureFailureCount())) {
                        return "考试人脸抓拍检测失败次数不能为空";
                    }
                }
            }
        }

        return null;
    }

    /**
     * 考试-添加
     *
     * @param req 考试-添加对象
     * @return 添加结果
     */
    public Result<String> saveRegistration(AdminExamRegistrationSaveReq req) {
        Exam exam = BeanUtil.copyProperties(req, Exam.class);
        exam.setExamType(ExamTypeEnum.EXAMINATION.getCode());
        // 参数校验
        String message = registrationSaveUpdateParamValid(exam, Boolean.FALSE);
        if (StrUtil.isNotBlank(message)) {
            return Result.error(message);
        }

        RLock lock = redissonClient.getLock(RedisConstant.Exam.EXAM);
        try {
            boolean lockRes = lock.tryLock(RedisConstant.DEFAULT_WAIT_TIME, RedisConstant.DEFAULT_LEASE_TIME, TimeUnit.SECONDS);
            if (!lockRes) {
                log.warn("添加考试获取锁超时");
                return Result.error(toI18nFail());
            }
            Integer maxSort = dao.getMaxSort();
            exam.setSort(maxSort + 1);
            exam.setCountVirtualBuy(0);
            exam.setExamCount(1);

            boolean saveRes = dao.save(exam);
            return saveRes ? Result.success(toI18nSuccess()) : Result.error(toI18nFail());
        } catch (InterruptedException e) {
            log.error("添加考试信息失败", e);
            Thread.currentThread().interrupt();
            throw new BaseException(toI18nFail());
        } finally {
            // 释放锁
            lock.unlock();
        }
    }


    /**
     * 考试-添加或更新参数校验
     *
     * @param exam 添加对象
     * @return 错误信息
     */
    private String registrationSaveUpdateParamValid(Exam exam, Boolean isUpdate) {
        Paper paper = paperDao.getById(exam.getPaperId());
        if (ObjectUtil.isNull(paper)) {
            return "试卷不存在";
        }

        // 有效期
        if (ValidityDateTypeEnum.FIXED_DAY.getCode().equals(exam.getValidityDateType())) {
            if (ObjectUtil.isNull(exam.getDays())) {
                return "有效天数不能为空";
            }
        } else if (ValidityDateTypeEnum.FIXED_PERIOD.getCode().equals(exam.getValidityDateType())) {
            if (ObjectUtil.isNull(exam.getBeginTime()) || ObjectUtil.isNull(exam.getEndTime())) {
                return "考试（开始时间/结束时间）不能为空";
            }
            if (exam.getBeginTime().isAfter(exam.getEndTime())) {
                return "考试结束时间不能小于开始时间";
            }
            exam.setEndTime(exam.getEndTime().plusSeconds(59));
        }

        // 切屏限制
        if (Boolean.TRUE.equals(exam.getSwitchScreenLimit())) {
            if (ObjectUtil.isNull(exam.getSwitchScreenCount())) {
                return "考试切换屏数不能为空";
            }
            if (ObjectUtil.isNull(exam.getSwitchScreenTime())) {
                return "考试切换屏判断时间不能为空";
            }
        }

        // 成绩展示
        if (Boolean.TRUE.equals(exam.getResultShow())) {
            if (ObjectUtil.isNull(exam.getResultShowRule())) {
                return "考试成绩展示规则不能为空";
            }
            if (ResultShowRuleEnum.CUSTOM.getCode().equals(exam.getResultShowRule()) && ObjectUtil.isNull(exam.getResultShowTime())) {
                return "考试成绩展示时间不能为空";
            }

            if (Boolean.TRUE.equals(exam.getAnswerShow())) {
                if (ObjectUtil.isNull(exam.getAnswerShowRule())) {
                    return "作答内容展示规则不能为空";
                }
            }
        } else {
            exam.setAnswerShow(Boolean.FALSE);
            exam.setResultShowRank(Boolean.FALSE);
        }

        // 人脸抓拍
        if (Boolean.TRUE.equals(exam.getFaceCapture())) {
            if (ObjectUtil.isNull(exam.getFaceCaptureTime())) {
                return "考试人脸抓拍时间不能为空";
            }

            if (Boolean.FALSE.equals(exam.getFaceId())) {
                exam.setFaceCaptureDetection(Boolean.FALSE);
                exam.setFaceCaptureHighDetection(Boolean.FALSE);
            } else {
                if (Boolean.TRUE.equals(exam.getFaceCaptureDetection())) {
                    if (ObjectUtil.isNull(exam.getFaceCaptureFailureCount())) {
                        return "考试人脸抓拍检测失败次数不能为空";
                    }
                }
            }
        }

        // 报名时间
        if (exam.getRegistrationStartTime().isAfter(exam.getRegistrationEndTime())) {
            return "考试报名结束时间不能小于开始时间";
        } else {
            exam.setRegistrationEndTime(exam.getRegistrationEndTime().plusSeconds(59));
        }

        // 考试开始时间
        if (exam.getRegistrationEndTime().isAfter(exam.getExamStartTime())) {
            return "考试开始时间不能小于报名结束时间";
        } else {
            exam.setExamEndTime(exam.getExamStartTime().plusMinutes(exam.getAnswerTime()).plusSeconds(59));
        }

        // 允许迟到
        if (Boolean.TRUE.equals(exam.getAllowableLate())) {
            if (ObjectUtil.isNull(exam.getAllowableLateTime())) {
                return "考试允许迟到时间不能为空";
            }
            if (exam.getAllowableLateTime() >= exam.getAnswerTime()) {
                return "考试允许迟到时间不能大于等于作答时长";
            }
        } else {
            if (Boolean.FALSE.equals(isUpdate)) {
                exam.setAllowableLate(Boolean.FALSE);
                exam.setAllowableLateTime(ObjectUtil.isNotNull(exam.getAllowableLateTime()) ? exam.getAllowableLateTime() : 1);
            }
        }

        // 人脸识别
        if (Boolean.TRUE.equals(exam.getFaceId())) {
            if (ObjectUtil.isNull(exam.getFaceIdLimitTime())) {
                exam.setFaceIdLimitTime(15);
            } else {
                if (exam.getFaceIdLimitTime() < 1 || exam.getFaceIdLimitTime() > 30) {
                    return "考试开考前人脸识别限制时间需要大于等于1且小于等于15分钟";
                }
            }

            if (ObjectUtil.isNull(exam.getFaceIdLimitCount())) {
                exam.setFaceIdLimitCount(3);
            } else {
                if (exam.getFaceIdLimitCount() < 1 || exam.getFaceIdLimitCount() > 99) {
                    return "考试开考前人脸识别限制次数需要大于等于1且小于等于99次";
                }
            }

            exam.setFaceIdFailureEnter(Optional.ofNullable(exam.getFaceIdFailureEnter()).orElse(Boolean.FALSE));
            exam.setFaceIdFailureAppeal(Optional.ofNullable(exam.getFaceIdFailureAppeal()).orElse(Boolean.FALSE));
        } else {
            if (Boolean.FALSE.equals(isUpdate)) {
                exam.setFaceIdLimitTime(Optional.ofNullable(exam.getFaceIdLimitTime()).orElse(15));
                exam.setFaceIdLimitCount(Optional.ofNullable(exam.getFaceIdLimitCount()).orElse(3));
            }
            exam.setFaceIdFailureEnter(Boolean.FALSE);
            exam.setFaceIdFailureAppeal(Boolean.FALSE);
        }

        // 通知
        if (Boolean.FALSE.equals(isUpdate)) {
            exam.setNoticeBefore(Optional.ofNullable(exam.getNoticeBefore()).orElse(Boolean.FALSE));
            exam.setNoticePublishedScore(Optional.ofNullable(exam.getNoticePublishedScore()).orElse(Boolean.FALSE));
        }
        return null;
    }

    /**
     * 考试-编辑
     *
     * @param req 考试-编辑对象
     * @return 编辑结果
     */
    public Result<String> editRegistration(AdminExamRegistrationEditReq req) {
        // 获取校验考试
        Exam exam = dao.getById(req.getId());
        if (ObjectUtil.isNull(exam) || !ExamTypeEnum.EXAMINATION.getCode().equals(exam.getExamType())) {
            return Result.error("考试不存在");
        }

        // 更新考试
        Exam updateExam = BeanUtil.copyProperties(req, Exam.class);

        // 参数校验
        String message = registrationSaveUpdateParamValid(updateExam, Boolean.TRUE);
        if (StrUtil.isNotBlank(message)) {
            return Result.error(message);
        }

        updateExam.setCountVirtualBuy(0);
        updateExam.setExamCount(1);
        updateExam.setGmtCreate(null);
        updateExam.setGmtModified(null);
        boolean updateRes = dao.updateById(updateExam);
        return updateRes ? Result.success(toI18nSuccess()) : Result.error(toI18nFail());
    }

    /**
     * 考试-报名/开考延期
     *
     * @param req 延期参数
     * @return 延期处理结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<String> postponement(AdminExamRegistrationPostponementReq req) {
        // 获取校验考试
        Exam exam = dao.getById(req.getId());
        if (ObjectUtil.isNull(exam) || !ExamTypeEnum.EXAMINATION.getCode().equals(exam.getExamType())) {
            return Result.error("考试不存在");
        }

        Exam examUpdate = BeanUtil.copyProperties(req, Exam.class);
        examUpdate.setExamEndTime(examUpdate.getExamStartTime().plusMinutes(exam.getAnswerTime()));
        dao.updateById(examUpdate);
        return Result.success(toI18nSuccess());
    }

    /**
     * 考试-批量提交延时
     *
     * @param req 设置参数
     * @return 设置结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<String> submitLateBatch(AdminExamRegistrationSubmitLateBatchReq req) {
        // 获取校验考试
        Exam exam = dao.getById(req.getExamId());
        if (ObjectUtil.isNull(exam) || !ExamTypeEnum.EXAMINATION.getCode().equals(exam.getExamType())) {
            return Result.error("考试不存在");
        }
        if (!exam.getExamEndTime().isAfter(LocalDateTime.now())) {
            return Result.error("考试已结束不可再延迟用户交卷");
        }

        // 校验选中用户状态
        List<ExamUser> examUserList = examUserDao.lambdaQuery()
                .eq(ExamUser::getExamType, exam.getExamType())
                .eq(ExamUser::getExamId, req.getExamId())
                .in(CollectionUtil.isNotEmpty(req.getUserIds()), ExamUser::getUserId, req.getUserIds())
                .eq(ExamUser::getUserExamStatus, UserExamStatusEnum.DURING_EXAM.getCode())
                .list();
        if (CollectionUtil.isEmpty(examUserList)) {
            return Result.error("选中的用户非考试中状态不可延迟用户交卷");
        }

        LocalDateTime endTime = req.getExamEndTime();
        if (req.getAutoLater()) {
            // 自动延期
            endTime = exam.getExamEndTime().plusMinutes(req.getLaterTime());
        }

        // 修改用户考试
        List<Long> examUserIdList = examUserList.stream().map(ExamUser::getId).collect(Collectors.toList());
        examUserDao.lambdaUpdate()
                .in(ExamUser::getId, examUserIdList)
                .set(ExamUser::getExamEndTime, endTime)
                .update();

        // 修改考试记录
        List<Long> userIds = examUserList.stream().map(ExamUser::getUserId).collect(Collectors.toList());
        examUserRecordDao.lambdaUpdate()
                .eq(ExamUserRecord::getExamType, exam.getExamType())
                .eq(ExamUserRecord::getRelateId, req.getExamId())
                .in(ExamUserRecord::getUserId, userIds)
                .set(ExamUserRecord::getEndTime, endTime)
                .update();
        return Result.success(toI18nSuccess());
    }
}
